using System;
using System.IO;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using dotnet;
using ssh;
using Renci.SshNet.Sftp;
using Renci.SshNet.Common;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace wspace
{
    public class LocalCmp
    {
        private SftpConnect Brg;
        private string ServerPath;
        private string LocalPath;
        private bool error;

        public List<string> LnewFList {get;}    /* 本地端新增的文件列表 */
        public List<string> LignoreList {get;}  /* 本地端忽略的文件列表 */

        public LocalCmp(SftpConnect brg, string serverpath, string localpath)
        {
            this.Brg = brg;
            this.ServerPath = serverpath;
            this.LocalPath = localpath;
            this.LnewFList = new List<string>(0);
            this.LignoreList = new List<string>(0);
        }

        public bool CmpLocalDiff()
        {
            bool ret = true;
            this.error = false;
            this.LnewFList.Clear();
            this.LignoreList.Clear();
            try
            {
                DirectoryInfo dinfo = new DirectoryInfo(this.LocalPath);
                List<string> ignorelist = new List<string>(0);
                this.IteratingLocalDir(dinfo, true, ignorelist);
                ret = !this.error;
            }
            catch (Exception e)
            {
                Utils.ErrorLog(e.Message);
                ret = false;
            }
            return ret;
        }

        private void IteratingLocalDir(DirectoryInfo dirinfo, bool checkfiles, List<string> ignorelist)
        {
            List<string> inheignore = new List<string>(0);  /* 子级过滤 */
            List<string> currignore = new List<string>(0);  /* 当前过滤 */

            string slash = Utils.GetSlash();
            string ignorefile = dirinfo.FullName + slash + Configs.IgnoreCfgName;

            AnalysisIgnoreFile(dirinfo, ignorelist, inheignore, currignore, ignorefile);

            bool ignored = false;
            if (checkfiles)
            {
                foreach (FileInfo file in dirinfo.GetFiles())
                {
                    foreach (string regular in currignore)
                    {
                        if (regular.EndsWith(slash)) continue;
                        try
                        {
                            ignored = Regex.IsMatch(file.Name, "^" + regular + "$");
                        }
                        catch (Exception) {ignored = false;}
                        if (ignored)
                        {
                            this.LignoreList.Add(file.FullName);
                            break;
                        }
                    }
                    if (ignored) continue;
                    SftpFile rfile = null;
                    try
                    {
                        rfile = this.Brg.sftp.Get(GenerateRemotePath(file.FullName.Replace('\\', '/')));
                    }
                    catch (SftpPathNotFoundException){}
                    catch (Exception e) {Utils.ErrorLog(e.Message); this.error = true; return;}
                    if (rfile == null || rfile.IsDirectory)
                    {
                        this.LnewFList.Add(file.FullName);
                    }
                }
            }

            foreach (DirectoryInfo dir in dirinfo.GetDirectories())
            {
                Utils.Platform platform = Utils.GetPlatform();
                if (platform == Utils.Platform.Linux)
                {
                    if (dir.Name.Equals(".") || dir.Name.Equals(".."))
                    {
                        continue;
                    }
                }
                if (dir.Name.Equals(Configs.ConfigDir))
                {
                    continue;
                }
                foreach (string regular in currignore)
                {
                    string reg = regular.EndsWith(slash) ? regular.Substring(0, regular.Length - 1) : regular;
                    try
                    {
                        ignored = Regex.IsMatch(dir.Name, "^" + reg + "$");
                    }
                    catch (System.Exception) {ignored = false;}
                    if (ignored)
                    {
                        this.LignoreList.Add(dir.FullName + slash);
                        break;
                    }
                }
                if (ignored) continue;
                SftpFile rdir = null;
                try
                {
                    rdir = this.Brg.sftp.Get(GenerateRemotePath(dir.FullName.Replace('\\', '/')));
                }
                catch (SftpPathNotFoundException){}
                catch (Exception e) {Utils.ErrorLog(e.Message); this.error = true; return;}
                if (rdir == null || !rdir.IsDirectory)
                {
                    this.LnewFList.Add(dir.FullName + Utils.GetSlash());
                    continue;
                }
                if (Utils.TimeCmp(rdir.LastWriteTime, dir.LastWriteTime) != 0)
                {
                    IteratingLocalDir(dir, true, inheignore);
                }
                else
                {
                    IteratingLocalDir(dir, false, inheignore);
                }
            }
        }

        private string GenerateRemotePath(string local)
        {
            string remote = local.Substring(this.LocalPath.Length);
            return this.ServerPath + remote;
        }

        /* List 深度复制 */
        private List<T> ListClone<T>(object List)
        {
            using (Stream objectStream = new MemoryStream())
            {
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize(objectStream, List);
                objectStream.Seek(0, SeekOrigin.Begin);
                return formatter.Deserialize(objectStream) as List<T>;
            }
        }

        private void AnalysisIgnoreFile(DirectoryInfo dirinfo, List<string> ignorelist, List<string> inheignorelist, List<string> currignorelist, string ignorefile)
        {
            string slash = Utils.GetSlash();
            foreach (string regular in ignorelist)
            {
                int slashidx = regular.IndexOf(slash);  // slashidx 不可能为 -1，存在ignorefile中的项，都不是对当前文件夹下的约束
                string cpname = regular.Substring(0, slashidx);
                if (cpname.Equals(dirinfo.Name))
                {
                    string subname = regular.Substring(slashidx + 1);
                    int subslashidx = subname.IndexOf(slash);
                    if (subslashidx == -1 || subslashidx == (subname.Length - 1))
                    {
                        currignorelist.Add(subname);
                    }
                    else
                    {
                        inheignorelist.Add(subname);
                    }
                }
            }
            if (File.Exists(ignorefile))
            {
                foreach (string regular in File.ReadLines(ignorefile))
                {
                    string reg = regular.Trim();
                    if (reg.Equals("") || reg.StartsWith('#')) continue;
                    reg = Utils.FormatPath(reg);
                    if (reg.StartsWith("." + slash))
                    {
                        reg = reg.Substring(2);
                    }
                    if (reg.Contains(slash) && reg.IndexOf(slash) != (reg.Length - 1))  // TODO 正则表达式中可能存在转义字符 \
                    {
                        inheignorelist.Add(reg);
                    }
                    else
                    {
                        currignorelist.Add(reg);
                    }
                }
            }
        }
    }
}